-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local s_unpack = string.unpack
local t_pack = table.pack
local bios_decoder = require 'bios.smbios.bios_decoder'
local processor_decoder = require 'bios.smbios.processor_decoder'
local memory_decoder = require 'bios.smbios.memory_decoder'
local memory_module_decoder = require 'bios.smbios.memory_module_decoder'
local cache_decoder = require 'bios.smbios.cache_decoder'
local memory_error_correction_decoder = require 'bios.smbios.memory_error_correction_decoder'
local file_sec = require 'utils.file'
local log = require 'mc.logging'

-- decoder只负责提供smbios数据的解析和传递的方法，不保存任何信息
local M = {}

local entry_collection = {}
entry_collection.__index =  entry_collection
function entry_collection.new()
    return setmetatable({}, entry_collection)
end
function entry_collection:add_entry(entry)
    local v = self[entry.Type]
    if not v then
        v = {}
        self[entry.Type] = v
    end
    v[#v + 1] = entry
end

function entry_collection:get_entries(type)
    return self[type]
end

local SMBiosInfoType = {
    BIOSInformation = 0,
    SystemInformation = 1,
    SystemEnclosure = 3,
    ProcessorInformation = 4,
    MemoryModuleInformation = 6,
    CacheInformation = 7,
    PortConnection = 8,
    SystemSlots = 9,
    PhysicalMemoryArray = 16,
    MemoryDevice = 17,
    MemoryArrayMappedAddress = 19,
    SystemBootInformation = 32,
    EndOfTable = 127
}

local DecoderMap = {
    [SMBiosInfoType.BIOSInformation] = bios_decoder.decode,
    [SMBiosInfoType.ProcessorInformation] = processor_decoder.decode,
    [SMBiosInfoType.MemoryDevice] = memory_decoder.decode,
    [SMBiosInfoType.MemoryModuleInformation] = memory_module_decoder.decode,
    [SMBiosInfoType.CacheInformation] = cache_decoder.decode,
    [SMBiosInfoType.PhysicalMemoryArray] = memory_error_correction_decoder.decode
}

-- 解析文件buffer的入口，根据识别到的类型不同再分别调用各设备的解析方法
local function decode_entry(buf, pos)
    pos = pos or 1
    local type, length, handle, decode_pos = s_unpack('BBH', buf, pos)
    if type == SMBiosInfoType.EndOfTable then
        return
    end

    local strings = {}
    local strings_pos = pos + length
    for s, p in string.gmatch(buf, '([^\0]*)\0()', strings_pos) do
        strings[#strings + 1] = s
        if buf:byte(p) == 0 then
            strings_pos = p + 1
        break
      end
    end

    local decoder = DecoderMap[type]
    if not decoder then
        return nil, strings_pos
    end

    local read_fileds = function(fmt)
        local r = t_pack(s_unpack(fmt, buf, decode_pos))
        decode_pos = r[#r]
        r[#r] = nil
        return r
    end
    return decoder(type, length, handle, read_fileds, strings), strings_pos
end

-- 将文件内容解析后逐条加载到collection内，供分类提取
function M.load(path)
    if not path then
        return nil
    end
    local f = file_sec.open_s(path, 'rb')
    if not f then
      return nil
    end
    local buf = f:read('a')
    f:close()
    local collection = entry_collection.new()
    local entry
    local pos = 1
    while pos and #buf - pos >= 5 do
        entry, pos = decode_entry(buf, pos)
        if entry then
            collection:add_entry(entry)
        end
    end
    return collection
end

-- 按照smbios协议解析后的信息集合，只与协议格式有关，与器件对象类定义无关，数据应用于对象还需上层进一步处理
function M.get_processor_devices(collection)
    local processor_collection = collection:get_entries(SMBiosInfoType.ProcessorInformation)
    if not processor_collection then
        return nil
    end
    local devices = {}
    for _, entry in ipairs(processor_collection) do
        local alias_name = processor_decoder.get_alias_name_in_smbios(entry)
        local fields = entry.Fields
        if alias_name and fields then
            devices[alias_name] = fields
        end
    end
    return devices
end

function M.get_memory_devices(collection)
    local memory_collection = collection:get_entries(SMBiosInfoType.MemoryDevice)
    if not memory_collection then
        return nil
    end
    local devices = {}
    local presence_memory = ''
    for _, entry in ipairs(memory_collection) do
        local fields = entry.Fields
        local alias_name
        -- bios上报0x03 Dram来表示HBM
        if fields and fields.memory_type == 0x03 then
            alias_name = fields.device_locator
        else
            alias_name = memory_decoder.get_alias_name_in_smbios(entry)
        end
        if alias_name and fields then
            devices[alias_name] = fields
        end
        if fields and fields.manufacturer ~= 'NO DIMM' and fields.manufacturer ~= 'empty' then
            presence_memory = #presence_memory == 0 and alias_name or 
                string.format('%s, %s', presence_memory, alias_name)
        end
    end
    log:notice('[bios]presence_memory alias_name is %s', presence_memory)
    return devices
end

function M.get_physical_memory_array(collection)
    local memory_collection = collection:get_entries(SMBiosInfoType.PhysicalMemoryArray)
    if not memory_collection then
        return nil
    end
    local devices = {}
    for _, entry in ipairs(memory_collection) do
        local fields = entry.Fields
        local location = fields and fields.location
        if location and fields then
            devices[location] = fields
        end
    end
    return devices
end

function M.get_cache_devices(collection)
    local cache_collection = collection:get_entries(SMBiosInfoType.CacheInformation)
    if not cache_collection then
        return nil
    end
    local devices = {}
    for _, entry in ipairs(cache_collection) do
        local alias_name = cache_decoder.get_alias_name_in_smbios(entry)
        local fields = entry.Fields
        if alias_name and fields then
            devices[alias_name] = fields
        end
    end
    return devices
end

function M.get_platform_firmware_info(collection)
    local platform_firmware_collection = collection:get_entries(SMBiosInfoType.BIOSInformation)
    if not platform_firmware_collection then
        return
    end

    -- platform firmware只有1个
    return platform_firmware_collection[1]
end

M.decode_entry = decode_entry
return M
